package com.ahaoyou.oom;

/**
 * @author LH
 * @create 2022/7/21 16:27
 * VM Args：-XX:PermSize=6M -XX:MaxPermSize=6M
 * jdk6 会出现 java.lang.OutOfMemoryError: PermGen space
 * 因为jdk6的常量池都是分配在永久代中，我们可以通过-XX：PermSize和-XX：MaxPermSize限制永久代的大小
 * <p>
 * 使用JDK 7或更高版本的JDK来运行这段程序并不会得到相同的结果，无论是在JDK 7中继续使
 * 用-XX：MaxPermSize参数或者在JDK 8及以上版本使用-XX：MaxMeta-spaceSize参数把方法区容量同样限制在6MB，
 * 也都不会重现JDK 6中的溢出异常，循环将一直进行下去，永不停歇[1]。出现这种变化，
 * 是因为自JDK 7起，原本存放在永久代的字符串常量池被移至Java堆之中，所以在JDK 7及以上版
 * 本，限制方法区的容量对该测试用例来说是毫无意义的，这时候使用-Xmx参数限制最大堆到6MB就能够看到效果
 */
public class RuntimeConstantPoolOOM {
    //    public static void main(String[] args) {
//        // 使用Set保持着常量池引用，避免Full GC回收常量池行为
//        Set<String> set = new HashSet<String>();
//        // 在short范围内足以让6MB的PermSize产生OOM了
//        short i = 0;
//        while (true) {
//            set.add(String.valueOf(i++).intern());
//        }
//    }
    public static void main(String[] args) {
//        这段代码在JDK 6中运行，会得到两个false，而在JDK 7中运行，会得到一个true和一个false。产
//        生差异的原因是，在JDK 6中，intern()方法会把首次遇到的字符串实例复制到永久代的字符串常量池
//        中存储，返回的也是永久代里面这个字符串实例的引用，而由StringBuilder创建的字符串对象实例在
//        Java堆上，所以必然不可能是同一个引用，结果将返回false。
//        而JDK 7（以及部分其他虚拟机，例如JRockit）的intern()方法实现就不需要再拷贝字符串的实例
//        到永久代了，既然字符串常量池已经移到Java堆中，那只需要在常量池里记录一下首次出现的实例引
//        用即可，因此intern()返回的引用和由StringBuilder创建的那个字符串实例就是同一个。而对str2比较返
//        回false，这是因为“java”这个字符串在执行String-Builder.toString()之前就已经在sun.misc.Version这个类出现过，字符串常量
//        池中已经有它的引用，不符合intern()方法要求“首次遇到”的原则，“计算机软件”这个字符串则是首次
//        出现的，因此结果返回true。
        //        在JDK 8以后，永久代便完全退出了历史舞台，元空间作为其替代者登场
        String str1 = new StringBuilder("计算机").append("软件").toString();
        System.out.println(str1.intern() == str1);
        String str2 = new StringBuilder("ja").append("va").toString();
        System.out.println(str2.intern() == str2);
    }
}
